﻿using System;
using System.Collections.Generic;
using System.Linq;
using Communication;
using Storage.Core;

namespace Storage.Client
{
    public class StorageClient<T> : IStorageClient<T>
    {
    	public StorageClient(ICommunicator communicator, ISerializer<T> serializer, IDeserializer<T> deserializer)
    	{
    		this.serializer = serializer;
    		this.deserializer = deserializer;
    		this.communicator = communicator;
    	}

    	public bool AddOrUpdate(IEntity<T> entity)
    	{
			byte[] binaryResponse = SendQuery(StorageQueryFactory.CreateAddOrUpdateQuery(entity, serializer));
			var response = AddOrUpdateResponse.Deserialize(binaryResponse);
    		return response.Added;
    	}

        public bool TryRead(Guid id, out IEntity<T> entity)
        {
			byte[] binaryResponse = SendQuery(StorageQueryFactory.CreateReadByIdQuery(id));
        	var response = ReadByIdResponse.Deserialize(binaryResponse);

        	entity = null;
			if (response.Result == null)
				return false;
        	entity = new Entity<T>(response.Result.Id, deserializer.Deserialize(response.Result.Body));
        	return true;
        }

        public IEnumerable<IEntity<T>> ReadAllOfType()
        {
			byte[] binaryResponse = SendQuery(StorageQueryFactory.CreateReadByTypeQuery<T>());
			var response = ReadByTypeResponse.Deserialize(binaryResponse);
        	return response.Entities.Select(plain => new Entity<T>(plain.Id, deserializer.Deserialize(plain.Body)));
        }

		public bool Delete(Guid id)
		{
			byte[] binaryResponse = SendQuery(StorageQueryFactory.CreateDeleteQuery(id));
			var response = DeleteResponse.Deserialize(binaryResponse);
			return response.Deleted;
		}

		public bool Delete(IEntity<T> entity)
		{
			return Delete(entity.Id);
		}

		private byte[] SendQuery(IStorageQuery query)
		{
			byte[] binaryResponse;
			try
			{
				binaryResponse = communicator.DoCommunicate(query.Serialize());
			}
			catch (Exception error)
			{
				throw new StorageNotAvailableException("Storage is currently not available.", error);
			}
			CheckResponse(binaryResponse);
			return binaryResponse;
		}

		private static void CheckResponse(byte[] response)
		{
			if (response.Length <= 0)
				throw new StorageException("Response from server has zero length.");
			if (response[0] == StorageOpCodes.Error)
			{
				var errorResponse = ErrorResponse.Deserialize(response);
				throw new StorageException(String.Format("Storage encountered an error: {0}", errorResponse.ErrorDescription));
			}
		}

    	private readonly ISerializer<T> serializer;
    	private readonly IDeserializer<T> deserializer;
    	private readonly ICommunicator communicator;
    }
}